import { AnimationGroup } from "@babylonjs/core/Animations/animationGroup";
import { Animatable } from '@babylonjs/core/Animations/animatable';
import { CreateHandleTo, IHandle, RemoveHandle } from "../data/data-handle";
import { Utils } from "../tool/utils";
import { Scene } from "@babylonjs/core/scene";
import { AnimationFrame, ITimer } from './animation-frame';


/**
 * 权重动作参数对象
 * @param from 起始帧
 * @param to 结束帧
 * @param loop 是否循环
 * @param speedRatio 速度比
 * @param weight 权重
 * @param useDirectAnimation 使用直接播放模式（当权重模式无效时尝试使用）
 * @param callOnFirstAnimatable 结束回调函数由第一个animatable触发（默认最后一个）
 */
interface IWeightedActionOptions {
    from?: number,
    to?: number,
    loop?: boolean,
    speedRatio?: number,
    weight?: number,
    useDirectAnimation?: boolean,
    callOnFirstAnimatable?: boolean,
}


/**
 * 权重动作
 * @param animationGroup 动画组
 * @param options 参数对象
 */
class WeightedAction {

    private _options: IWeightedActionOptions;
    private _animatables: Array<Animatable> = [];
    private _animationGroup: AnimationGroup;
    private _doneHandles: IHandle<void>[] = [];
    private _endHandles: IHandle<void>[] = [];
    private _started = false;
    private _scene?: Scene;
    private _targetAnimatable!: Animatable;
    private _checkRequest = {};

    public readonly id = Utils.id;

    constructor(animationGroup: AnimationGroup, options?: IWeightedActionOptions, scene?: Scene) {
        this._animationGroup = animationGroup;
        this._scene = scene;
        this._options = this.initializeOptions(options);
        this.initializeAnimatables();
    }

    /**
     * 设置权重
     */
    public set weight(weight: number) {
        this._animatables.forEach(a => a.weight = weight);
    }

    /**
     * 获取权重
     */
    public get weight(): number {
        return this._animatables[0].weight;
    }

    /**
     * 实际起始帧
     */
    public get from(): number {
        return this._options.from!;
    }

    /**
     * 实际结束帧
     */
    public get to(): number {
        return this._options.to!;
    }

    /**
     * 当前帧
     */
    public get current(): number {
        return this._targetAnimatable.masterFrame;
    }

    /**
     * 设置速度比
     */
    public set speedRatio(ratio: number) {
        this._animatables.forEach(a => a.speedRatio = ratio);
    }

    /**
     * 获取速度比
     */
    public get speedRatio(): number {
        return this._targetAnimatable.speedRatio;
    }

    /**
     * 动作是否正在进行中
     */
    public get started(): boolean {
        return this._started;
    }

    /**
     * 开始动作
     */
    public start(): void {
        this._started = true;
        this._animatables.forEach(a => a.restart());
        this.checkStart(true);
    }

    /**
     * 停止动作
     * @param frame 动作停止到的值（默认为当前）
     */
    public stop(frame?: number): void {
        this._animatables.forEach(a => {
            if (frame !== undefined) a.goToFrame(frame);
            a.pause();
        });
        this._started = false;
        this.checkStart(false);
    }

    /**
     * 添加动作完成时的回调函数对象
     * @param func 回调函数
     * @returns 回调函数对象
     */
    public onAnimationDone(func: { (): void }): IHandle<void> {
        return CreateHandleTo<void>(func, this._doneHandles);
    }

    /**
     * 移除动作完成时的回调函数对象
     * @param handle 回调函数对象或其id
     */
    public removeAnimationDoneHandle(handle: number | IHandle<void>): void {
        RemoveHandle(handle, this._doneHandles);
    }

    /**
     * 添加动作结束时的回调函数对象
     * @param func 回调函数
     * @returns 回调函数对象
     */
    public onAnimationEnd(func: { (): void }): IHandle<void> {
        return CreateHandleTo<void>(func, this._endHandles);
    }

    /**
     * 移除动作结束时的回调函数对象
     * @param handle 回调函数对象或其id
     */
    public removeAnimationEndHandle(handle: number | IHandle<void>): void {
        RemoveHandle(handle, this._endHandles);
    }

    private initializeAnimatables(): void {
        const ops = this._options;
        for (const ta of this._animationGroup.targetedAnimations) {
            const tat = ta.target as any;
            const scn = tat.getScene ? tat.getScene() : this._scene;
            if (!scn) {
                console.warn('Neither the [scene] was found nor set!');
                continue;
            }
            const aa = ops.useDirectAnimation ?
                scn.beginDirectAnimation(ta.target, [ta.animation], ops.from!, ops.to!, ops.loop, ops.speedRatio) :
                scn.beginWeightedAnimation(ta.target, ops.from!, ops.to!, ops.weight!, ops.loop, ops.speedRatio);
            aa.pause();
            this._animatables.push(aa);
        }
        this._targetAnimatable = ops.callOnFirstAnimatable ? this._animatables[0] : this._animatables[this._animatables.length - 1];
        if (ops.loop) {
            this._targetAnimatable.onAnimationLoopObservable.add(() => {
                this._doneHandles.forEach(handle => handle.callback());
            });
        } else {
            this._targetAnimatable.onAnimationEndObservable.add(() => {
                this._doneHandles.forEach(handle => handle.callback());
            });
        }
    }

    private checkStart(begin: boolean): void {
        if (!begin) {
            AnimationFrame.cancel(this._checkRequest);
            return;
        }
        if (AnimationFrame.isRunning(this._checkRequest)) return;
        let pf = this.current;
        AnimationFrame.request(() => {
            const cur = this.current;
            if (cur < pf) {
                this._endHandles.forEach(handle => handle.callback());
            }
            pf = cur;
        }, 16, 200, this._checkRequest);
    }

    private initializeOptions(options?: IWeightedActionOptions): IWeightedActionOptions {
        const ops = options || {};
        return {
            from: ops.from === undefined ? 0 : ops.from,
            to: ops.to === undefined ? 1 : ops.to,
            loop: !!ops.loop,
            speedRatio: ops.speedRatio || 1,
            weight: ops.weight || 0,
            useDirectAnimation: !!ops.useDirectAnimation,
            callOnFirstAnimatable: !!ops.callOnFirstAnimatable,
        };
    }

    /**
     * 切换动作
     * @param actions 受影响的动作集合
     * @param request 动画对象
     * @param speed 切换速度（按比，小于等于0时终止所有动作）
     * @param indices 目标动作
     * @param weights 目标动作对应的目标权重
     * @param callback 切换完成时的回调函数
     * @returns 
     */
    public static Transmit(actions: WeightedAction[], request: ITimer, speed: number, indices?: number[], weights?: number[], callback?: { (): void }): void {
        if (speed <= 0) {
            AnimationFrame.cancel(request);
            actions.forEach(a => a.stop());
            return;
        }

        let count = 0;
        const length = actions.length;
        const iws = new Float32Array(length);
        const iss = new Float32Array(length);
        const ids: number[] = indices || [];
        for (let i = 0; i < length; i++) {
            const ii = ids.indexOf(i);
            if (ii < 0) {
                iws[i] = -actions[i].weight;
            } else {
                iws[i] = (weights && weights[ii] !== undefined) ? Math.max(Math.min(weights[ii], 1), 0) : 1;
                iws[i] = iws[i] - actions[i].weight;
            }
            iss[i] = iws[i] * speed;
        }
        actions.forEach(ac => ac.start());

        AnimationFrame.request(() => {
            for (let i = 0; i < actions.length; i++) {
                const wa = actions[i];
                wa.weight += iss[i];
                if (wa.weight <= 1e-6) {
                    wa.weight = 0;
                    wa.stop();
                }
            }
            count += speed;
            if (count > 1) {
                if (callback) callback();
                return true;
            }
        }, 16, 200, request);
    }

    /**
     * 比较是否同一动作
     * @param a 动作a
     * @param b 动作b
     * @returns 是否相同
     */
    public static Compare(a: WeightedAction, b: WeightedAction): boolean {
        return a.from === b.from && a.to === b.to;
    }

}


export { WeightedAction };
export type { IWeightedActionOptions };